﻿

namespace Oddo.Concept.Crm.Host.Console
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Security.Policy;
    using Oddo.Concept.Crm.Common.Communication.Controllers;
    
    public interface IControllerCatalog
    {
        List<IApiControllerCatalogItem> Items { get; }
        void Initialize();
    }

    public abstract class ControllerCatalog : IControllerCatalog
    {
        protected ControllerCatalog()
        {
            Items = new List<IApiControllerCatalogItem>();
        }

        public List<IApiControllerCatalogItem> Items { get; set; }
        public abstract void Initialize();
    }

    public interface IApiControllerCatalogItem
    {
        string Name { get; set; }
        string Type { get; set; }
    }

    [Serializable]
    public class ApiControllerCatalogItem : IApiControllerCatalogItem
    {
        private string _name;
        private string _type;

        public ApiControllerCatalogItem(string name, string type)
        {
            _name = name;
            _type = type;
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        
        public string Type
        {
            get { return _type; }
            set { _type = value; }
        }
    }


    public class DirectoryControllerCatalog : ControllerCatalog
    {
        private readonly string _path;

        public DirectoryControllerCatalog(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException("path");

            _path = path;
        }

        public string ApiControllersPath
        {
            get { return _path; }
        }

        public override void Initialize()
        {
            BuildCatalog();
        }
        
        private void BuildCatalog()
        {
            if(string.IsNullOrWhiteSpace(ApiControllersPath))
                throw new InvalidOperationException("Path cannot be null");
            if (!Directory.Exists(ApiControllersPath))
                throw new InvalidOperationException("Directory doesn't exist");

            AppDomain domain = this.BuildChildDomain(AppDomain.CurrentDomain);
            try
            {
                var list = new List<string>();
                var assemblies = (
                                     from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
                                     where !(assembly is System.Reflection.Emit.AssemblyBuilder)
                                        && assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
                                        && !String.IsNullOrEmpty(assembly.Location)
                                     select assembly.Location
                                 ).ToArray();


                list.AddRange(assemblies);
                Type loaderType = typeof(InnerApiControllerInfoLoader);
                if (loaderType.Assembly != null)
                {
                    var loader = (InnerApiControllerInfoLoader)domain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
                    loader.LoadAssemblies(assemblies);
                    this.Items.AddRange(loader.GetApiControllerInfos(this.ApiControllersPath));
                }
                
            }
            finally
            {
                AppDomain.Unload(domain);
            }
        }

        protected virtual AppDomain BuildChildDomain(AppDomain parentDomain)
        {
            if (parentDomain == null)
                throw new ArgumentNullException("parentDomain");
            else
                return AppDomain.CreateDomain("DiscoveryRegion", new Evidence(parentDomain.Evidence), parentDomain.SetupInformation);
        }

        private class InnerApiControllerInfoLoader : MarshalByRefObject
        {
            internal IEnumerable<IApiControllerCatalogItem> GetApiControllerInfos(string path)
            {
                var directory = new DirectoryInfo(path);
                var resolveEventHandler = (ResolveEventHandler)((sender, args) => OnReflectionOnlyResolve(args, directory));
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
                var assemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().Where(a => a.FullName == typeof (IApiController).Assembly.FullName);
                Type type = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(asm => asm.FullName == typeof (IApiController).Assembly.FullName).GetType(typeof(IApiController).FullName);
                ApiControllerCatalogItem[] moduleInfoArray = GetNotAllreadyLoadedApiControllerCatalogItem(directory, type).ToArray();
                AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
                return moduleInfoArray;
            }

            internal void LoadAssemblies(IEnumerable<string> assemblies)
            {
                foreach (string assemblyFile in assemblies)
                {
                    try
                    {
                        Assembly.ReflectionOnlyLoadFrom(assemblyFile);
                    }
                    catch (FileNotFoundException)
                    {
                    }
                }
            }

            private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
            {
                Assembly assembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
                if (assembly != null)
                    return assembly;
                var assemblyName = new AssemblyName(args.Name);
                string str = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
                return File.Exists(str) 
                    ? Assembly.ReflectionOnlyLoadFrom(str) 
                    : Assembly.ReflectionOnlyLoad(args.Name);
            }

            private static IEnumerable<ApiControllerCatalogItem> GetNotAllreadyLoadedApiControllerCatalogItem(DirectoryInfo directory, Type apiControllerType)
            {
                var list = new List<FileInfo>();
                Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
                foreach (FileInfo fileInfo in directory.GetFiles("*.dll")
                    .Where(file => alreadyLoadedAssemblies.FirstOrDefault(assembly => string.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == (Assembly)null))
                {
                    try
                    {
                        Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName);
                        list.Add(fileInfo);
                    }
                    catch (BadImageFormatException)
                    {
                    }
                }
                return list.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName).GetExportedTypes().Where(apiControllerType.IsAssignableFrom).Where(t => t != apiControllerType).Where(t => !t.IsAbstract).Select(CreateApiControllerInfo));
            }

            private static ApiControllerCatalogItem CreateApiControllerInfo(Type type)
            {
                return new ApiControllerCatalogItem(type.Name, type.AssemblyQualifiedName);
            }
        }

    }
}